1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use crate::co;
use crate::decl::*;
use crate::guard::*;
use crate::prelude::*;

/// Access types for [`File::open`](crate::File::open) and
/// [`FileMapped::open`](crate::FileMapped::open).
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum FileAccess {
	/// Opens the file as read-only. Fails if the file doesn't exist.
	ExistingReadOnly,
	/// Opens the file as read/write. Fails if the file doesn't exist.
	ExistingRW,
	/// Opens the file as read/write. If the file doesn't exist, it will be
	/// created.
	OpenOrCreateRW,
	/// Creates a new file as read/write. Fails if the file already exists.
	CreateRW,
}

impl std::fmt::Display for FileAccess {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "{}", match self {
			FileAccess::ExistingReadOnly => "Existing file, read-only",
			FileAccess::ExistingRW => "Existing file, read and write",
			FileAccess::OpenOrCreateRW => "Open existing file or create new file, read and write",
			FileAccess::CreateRW => "Create new file, read and write",
		})
	}
}

//------------------------------------------------------------------------------

/// Manages an [`HFILE`](crate::HFILE) handle, which provides file read/write
/// and other operations. It is closed automatically when the object goes out of
/// scope.
///
/// This is an alternative to the standard [`std::fs::File`], with a possibly
/// faster implementation since it's Windows-only.
///
/// If you just want to read the file, consider memory-mapping it with
/// [`FileMapped`](crate::FileMapped), which tends to be faster.
///
/// # Examples
///
/// Reading the contents as a string:
///
/// ```no_run
/// use winsafe::{self as w, prelude::*};
///
/// let f = w::File::open(
///     "C:\\Temp\\foo.txt",
///     w::FileAccess::ExistingRW,
/// )?;
/// let raw_bytes = f.read_all()?;
/// let text = w::WString::parse(&raw_bytes)?.to_string();
/// # w::SysResult::Ok(())
/// ```
///
/// Erasing the file and writing a string:
///
/// ```no_run
/// use winsafe::{self as w, prelude::*};
///
/// let f = w::File::open(
///     "C:\\Temp\\foo.txt",
///     w::FileAccess::OpenOrCreateRW,
/// )?;
/// f.set_size(0)?; // truncate
/// f.write("My text".as_bytes())?;
/// # w::SysResult::Ok(())
/// ```
pub struct File {
	hfile: CloseHandleGuard<HFILE>,
}

impl File {
	/// Opens a file with the desired access.
	#[must_use]
	pub fn open(file_path: &str, access: FileAccess) -> SysResult<Self> {
		let (acc, share, disp) = match access {
			FileAccess::ExistingReadOnly => (
				co::GENERIC::READ,
				Some(co::FILE_SHARE::READ),
				co::DISPOSITION::OPEN_EXISTING,
			),
			FileAccess::ExistingRW => (
				co::GENERIC::READ | co::GENERIC::WRITE,
				None,
				co::DISPOSITION::OPEN_EXISTING,
			),
			FileAccess::OpenOrCreateRW => (
				co::GENERIC::READ | co::GENERIC::WRITE,
				None,
				co::DISPOSITION::OPEN_ALWAYS,
			),
			FileAccess::CreateRW => (
				co::GENERIC::READ | co::GENERIC::WRITE,
				None,
				co::DISPOSITION::CREATE_NEW,
			),
		};

		let (hfile, _) = HFILE::CreateFile(
			file_path, acc, share, None, disp,
			co::FILE_ATTRIBUTE::NORMAL, None, None, None)?;
		Ok(Self { hfile })
	}

	/// Returns the underlying file handle.
	#[must_use]
	pub fn hfile(&self) -> &HFILE {
		&*self.hfile
	}

	/// Returns the position of the file pointer by calling
	/// [`HFILE::SetFilePointerEx`](crate::prelude::kernel_Hfile::SetFilePointerEx).
	#[must_use]
	pub fn pointer_offset(&self) -> SysResult<u64> {
		self.hfile.SetFilePointerEx(0, co::FILE_STARTING_POINT::CURRENT) // https://stackoverflow.com/a/17707021/6923555
			.map(|off| off as _)
	}

	/// Calls [`HFILE::ReadFile`](crate::prelude::kernel_Hfile::ReadFile) to
	/// read at most `buffer.len()` bytes from the file, starting at the current
	/// file pointer offset. Returns how many bytes were actually read. The file
	/// pointer is then incremented by the number of bytes read.
	///
	/// Note that the API limits the reading up to 4 GB.
	pub fn read(&self, buffer: &mut [u8]) -> SysResult<u32> {
		self.hfile.ReadFile(buffer)
	}

	/// Returns the size of the file by calling
	/// [`HFILE::GetFileSizeEx`](crate::prelude::kernel_Hfile::GetFileSizeEx),
	/// allocates the `Vec` buffer, then reads all the file bytes by calling
	/// [`HFILE::ReadFile`](crate::prelude::kernel_Hfile::ReadFile).
	///
	/// Note that the API limits the reading up to 4 GB.
	#[must_use]
	pub fn read_all(&self) -> SysResult<Vec<u8>> {
		self.set_pointer_offset(0)?;
		let mut buf = vec![0x00; self.size()? as _];
		self.read(&mut buf)?;
		Ok(buf)
	}

	/// Sets the position of the file pointer by calling
	/// [`HFILE::SetFilePointerEx`](crate::prelude::kernel_Hfile::SetFilePointerEx).
	pub fn set_pointer_offset(&self, offset: u64) -> SysResult<()> {
		self.hfile.SetFilePointerEx(offset as _, co::FILE_STARTING_POINT::BEGIN)
			.map(|_| ())
	}

	/// Truncates or expands the file by calling
	/// [`HFILE::SetFilePointerEx`](crate::prelude::kernel_Hfile::SetFilePointerEx)
	/// and
	/// [`HFILE::SetEndOfFile`](crate::prelude::kernel_Hfile::SetEndOfFile),
	/// then sets the file pointer to the beginning of the file.
	///
	/// If the size is increased, the contents in the new area are undefined.
	pub fn set_size(&self, num_bytes: u64) -> SysResult<()> {
		self.set_pointer_offset(num_bytes)?;
		self.hfile.SetEndOfFile()?;
		self.set_pointer_offset(0)
	}


	/// Returns the size of the file by calling
	/// [`HFILE::GetFileSizeEx`](crate::prelude::kernel_Hfile::GetFileSizeEx).
	#[must_use]
	pub fn size(&self) -> SysResult<u64> {
		self.hfile.GetFileSizeEx()
	}

	/// Returns the creation and last write times of the file, in the current
	/// time zone.
	#[must_use]
	pub fn times(&self) -> SysResult<(SYSTEMTIME, SYSTEMTIME)> {
		let (mut ft_creation, mut ft_last_write) = (FILETIME::default(), FILETIME::default());
		self.hfile.GetFileTime(Some(&mut ft_creation), None, Some(&mut ft_last_write))?;

		let st_creation_utc = FileTimeToSystemTime(&ft_creation)?;
		let st_last_write_utc = FileTimeToSystemTime(&ft_last_write)?;

		let st_creation_local = SystemTimeToTzSpecificLocalTime(None, &st_creation_utc)?;
		let st_last_write_local = SystemTimeToTzSpecificLocalTime(None, &st_last_write_utc)?;

		Ok((st_creation_local, st_last_write_local))
	}

	/// Writes the bytes at the current file pointer by calling
	/// [`HFILE::WriteFile`](crate::prelude::kernel_Hfile::WriteFile).
	///
	/// This method will fail if the file was opened with
	/// [`FileAccess::ExistingReadOnly`](crate::FileAccess::ExistingReadOnly).
	pub fn write(&self, data: &[u8]) -> SysResult<()> {
		self.hfile.WriteFile(data)
			.map(|_| ())
	}
}